package pl.edu.mimuw.irs.core.model.analysis.workflow;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import pl.edu.mimuw.irs.core.global.IDManager;
import pl.edu.mimuw.irs.core.model.analysis.TPair;
import pl.edu.mimuw.irs.core.model.analysis.resources.RequirementPlace;
import pl.edu.mimuw.irs.core.model.analysis.resources.ResourcePlace;
import pl.edu.mimuw.irs.core.model.design.net.IRSRequirement;
import pl.edu.mimuw.irs.core.model.design.net.IRSWorkflow;
import pl.edu.mimuw.irs.core.model.design.resource.Requirement;
import pl.edu.mimuw.irs.core.model.design.resource.Resource;
import pl.edu.mimuw.irs.core.model.petrinet.Arc;
import pl.edu.mimuw.irs.core.model.petrinet.Transition;
import pl.edu.mimuw.irs.core.util.Position;

/**
 * @author Jakub Rauch
 * Created on: 2009-05-27
 */
public class ResourcesWorkflow extends GlobalElementsWorkflow {

	private static final long serialVersionUID = -5576375380918448431L;
	
	// *** Attributes
	
	private Map<Resource, ResourcePlace> resourcesMap;
	private Map<Requirement, RequirementPlace> requirementsMap;
	private LinkedHashMap<ResourcePlace, TPair> resourceHiringMap;
	private LinkedHashMap<RequirementPlace, Set<ResourcePlace>> requirementsResourcesMap;
	public Map<Resource, ResourcePlace> getResourcesMap() {
		if (resourcesMap == null) resourcesMap = 
			new LinkedHashMap<Resource, ResourcePlace>();
		return resourcesMap;
	}
	public Map<Requirement, RequirementPlace> getRequirementsMap() {
		if (requirementsMap == null) requirementsMap = 
			new LinkedHashMap<Requirement, RequirementPlace>();
		return requirementsMap;
	}
	public LinkedHashMap<ResourcePlace, TPair> getResourceHiringMap() {
		if (resourceHiringMap == null) resourceHiringMap = 
			new LinkedHashMap<ResourcePlace, TPair>();
		return resourceHiringMap;
	}
	public LinkedHashMap<RequirementPlace, Set<ResourcePlace>> getRequirementsResourcesMap() {
		if (requirementsResourcesMap == null) requirementsResourcesMap = 
			new LinkedHashMap<RequirementPlace, Set<ResourcePlace>>();
		return requirementsResourcesMap;
	}
	
	// *** Constructors
	
	public ResourcesWorkflow(IRSWorkflow workflow) {
		super(workflow);
		
		/*
		 * Create places for resources, transitions for their hiring/firing 
		 * and fill mapping in resourcesMap.
		 */
		createResources(workflow.getResources());
		
		/*
		 * For all requirements existing in workflow create places and
		 * link them with the current resource places and hiring/firing 
		 * transitions.
		 */
		createRequirements(workflow.getRequirements());
		
	}
	
	// *** Methods
	
	private void createResources(Set<Resource> resources) {
		IDManager idManager = getIdManager();
		Map<Resource, ResourcePlace> resourcesMap = getResourcesMap();
		Map<ResourcePlace, TPair> hiringMap = getResourceHiringMap();
		
		Position pos = getIn().getPosition().under(2);
		for (Resource resource : resources) {
			/*
			 * Here we treat every resource separately - because
			 * they can have different costs, and this is easier 
			 * this way.
			 */
			Transition hiring = new Transition(idManager, pos.right(2));
			Transition firing = new Transition(idManager, hiring.getPosition().right());
			ResourcePlace place = new ResourcePlace(idManager, resource, hiring, firing, pos);
			pos = pos.under();
			addPlace(place);
			addTransition(hiring);
			addTransition(firing);
			resourcesMap.put(resource, place);
			hiringMap.put(place, new TPair(hiring, firing));
			addArc(getInitialize(), place);
			addArc(place, getFinalize());
			addArc(place, hiring);
			addArc(firing, place);
		}
	}

	private void createRequirements(Set<IRSRequirement> requirements) {
		IDManager idManager = getIdManager();
		Map<Requirement, RequirementPlace> requirementsMap = getRequirementsMap();
		Position pos = getIn().getPosition().under(2).right();
		
		/*
		 * Every IRSRequirement defines a set of requirements. To find them all we
		 * iterate over every IRSRequirement in the workflow and for each Requirement
		 * that has not got its RequirementPlace - we create it (however we treat similar
		 * requirements as the same). 
		 */
		for (IRSRequirement requirement : requirements) {
			for (Entry<Requirement, Integer> entry : 
				requirement.getRequirementsMap().entrySet()) {
				RequirementPlace place = requirementsMap.get(entry.getKey());
				if (place == null) {
					place = new RequirementPlace(idManager, entry.getKey(), pos);
					addPlace(place);
					requirementsMap.put(entry.getKey(), place);
					pos = pos.under();
					/*
					 * We created new place, so we can link it to all resources
					 * that meet this requirement, and fill it with tokens.
					 */
					linkRequirement(place);
				}
			}
		}
	}
	
	private void linkRequirement(RequirementPlace place) {
		Map<ResourcePlace, TPair> hiringMap = getResourceHiringMap();
		
		for (Entry<Resource, ResourcePlace> entry : getResourcesMap().entrySet()) {
			if (entry.getKey().meets(place.getRequirement())) {
				/*
				 * Link to numerous maps.
				 */
				entry.getValue().getMetRequirements().add(place);
				place.getMeetingResources().add(entry.getValue());
				Set<ResourcePlace> places = 
					getRequirementsResourcesMap().get(place.getRequirement());
				if (places == null) {
					places = new LinkedHashSet<ResourcePlace>();
					getRequirementsResourcesMap().put(place, places);
				}
				places.add(entry.getValue());
				/*
				 * If this requirement has already been linked, we just add weight to
				 * existing arc, otherwise we create new one.
				 */
				Arc initArc = null;
				Arc finalArc = null;
				for (Arc arc : place.getIncomingArcs()) {
					if (arc.getTransition().equals(getInitialize())) {
						initArc = arc;
						break;
					}
				}
				for (Arc arc : place.getOutgoingArcs()) {
					if (arc.getTransition().equals(getFinalize())) {
						finalArc = arc;
						break;
					}
				}
				if (initArc == null && finalArc == null) {
					addArc(getInitialize(), place, 1);
					addArc(place, getFinalize(), 1);
				} else {
					initArc.setWeight(initArc.getWeight() + 1);
					finalArc.setWeight(finalArc.getWeight() + 1);
				}
				/*
				 * Linking hiring/firing
				 */
				TPair pair = hiringMap.get(entry.getValue());
				addArc(place, pair.getHire(), 1);
				addArc(pair.getFire(), place, 1);
			}
		}
	}
	
	/*
	private void linkRequirementSubnet(RequirementSubnet subnet) {
		Transition initialize = getInitialize();
		Transition finalize = getFinalize();
		Position position = null;
		
		// Roles places and transitions
		Map<Role, Arc> initializeArcs = new HashMap<Role, Arc>();
		Map<Role, Arc> finalizeArcs = new HashMap<Role, Arc>();
		for (RolePlace place : subnet.getRolePlaces()) {
			Arc initializeArc = initializeArcs.get(place.getRole());
			Arc finalizeArc = finalizeArcs.get(place.getRole());
			// Always both exists or both do not exist (as well as the place).
			if (initializeArc == null || finalizeArc == null) {
				addPlace(place);
				initializeArc = new Arc(initialize, place);
				addArc(initializeArc);
				initializeArcs.put(place.getRole(), initializeArc);
				finalizeArc = new Arc(place, finalize);
				addArc(finalizeArc);
				finalizeArcs.put(place.getRole(), finalizeArc);
				if (position == null) position = new Position(60, 160);
				else position = new Position(position.getX(), position.getY() + 50);
				place.setPosition(position);
			} else {
				initializeArc.setWeight(initializeArc.getWeight() + 1);
				finalizeArc.setWeight(finalizeArc.getWeight() + 1);
			}
		}
		
		if (position == null) position = new Position(60, 160);
		else position = new Position(position.getX(), position.getY() + 50);
		
		// Resource places and trasitions
		for (ResourcePlace place : subnet.getResourcePlaces()) {
			addPlace(place);
			addArc(initialize, place);
			addArc(place, finalize);
			if (position == null) position = new Position(60, 160);
			else position = new Position(position.getX(), position.getY() + 50);
			place.setPosition(position);
		}
	}	
	*/
}
